/* ************************************************
 * Stimuli structure
 *
 * stimuli.pde
 * ------------------------------------------------
 */

import java.util.*;

// colors
// ==========================================
color QUESTION_BACKGROUND		= 0;
color QUESTION_FOREGROUND		= 255;

// time
// ============================================
static final float TRANSITION_TIME	= 2.0;

// stimulus presentation phase
// ===========================
static final int PHASE_START		= 9;
static final int PHASE_QUESTION		= 1;
static final int PHASE_VIS		= 2;
static final int PHASE_RESPONSE		= 3;
static final int PHASE_TRANSITION	= 4;
static final int PHASE_FINISHED		= 5;

static final int DRAW_MODE_GRAPH		= 1;
static final int DRAW_MODE_ICICLE	= 2;
static final int DRAW_MODE_TREEMAP	= 3;

static final int TASK_COUNT_CHILDREN	= 1;
static final int TASK_SHARED_PARENT	= 2;
static final int TASK_LARGEST_CHILDREN	= 3;
static final int TASK_BINARY_ANSWER	= 4;

// font for question display
PFont fontQuestion = null;
PFont fontTree = null;

class Stimulus
{
	int number;				// stimuli number (0 to N-1)
	String name;				// name of stimulus
	String treeFile;				// name of tree file
	String question;				// question to display
	ArrayList<String> hilights;		// names of nodes to hilight
	int drawMode;				// drawing mode
	int taskType;				// type of task
	boolean compatible;
	
	// the tree structure is held here
	Node theTree;
	
	// treemap structure
	TreeMap treeMap;
	
	// hilight map, used to quickly test if a node should be hilighted
	HashMap<String, Boolean> hilight_map;
	
	// answers by participant
	String answer;
	float readTime;
	float reasonTime;
	float responseTime;
	String strResponse;
	
	int transitionTimestamp;
	int questionTimestamp;
	int responseTimestamp;
	int visTimestamp;
	
	// bad response (only in training)
	int badTime;
	boolean flagBadResponse;
	
	
	// internal state keeping
	int state;
	float startTime;
	
	Stimulus(int _number, String _name, String _treeFile, String _question, ArrayList<String> _hilights, int _drawMode, String _answer, int _taskType)
	{
		number = _number;
		
		// initialize data and state
		name = _name;
		question = _question;
		answer = _answer;
		hilights = _hilights;
		drawMode = _drawMode;
		taskType = _taskType;
		treeFile = dataPath( tree_directory + "/" + _treeFile);

		// reset state
		state = PHASE_START;
		strResponse = "";
		
		// set bad response flag to clear
		flagBadResponse = false;
		
		// load the tree
		CachedFileReader input = new CachedFileReader( treeFile, null );
		
		treeDepth = -1;
		theTree = readTree( input, null, 0 );
		theTree.treeDepth = treeDepth + 1;
		input.closeFile();
		
		// layout the tree
		layout( theTree );
		theTree.global_maxChild = maxChild;
		
		// layout the treemap
		treeMap = new TreeMap( theTree );

		// create font if not already there
		if (fontQuestion == null)
		{
			fontQuestion = createFont( "SansSerif", 28.0 );
		}
		
		if (fontTree == null)
		{
			fontTree = createFont( "HelveticaBold", 14.0 );
		}
		
		// unpack hilights to map
		hilight_map = new HashMap<String, Boolean>();
		for (int i = 0; i < hilights.size(); i++)
		{
			hilight_map.put( hilights.get(i), true );
		}
	}
	
	boolean render()
	{
		switch (state)
		{
		case PHASE_START:
			startTime = getTime();
			state = PHASE_QUESTION;
			questionTimestamp = millis();
			
			break;
		
		case PHASE_QUESTION:
			renderQuestion();
			break;
		
		case PHASE_VIS:
			renderVis();
			break;
			
		case PHASE_RESPONSE:
			renderResponse();
			break;
			
		case PHASE_TRANSITION:
			renderTransition();
			break;
		
		case PHASE_FINISHED:
			return true;
		}
		
		return false;
		
	}
	
	void renderQuestion()
	{
		// dispaly question
		background( QUESTION_BACKGROUND );
		textFont(fontQuestion);
		textAlign(CENTER, CENTER);
			
		fill( QUESTION_FOREGROUND );
		stroke( QUESTION_FOREGROUND );
			
		text(question, width/2, height/2);
	}
	
	void renderVis()
	{
		// background
		background( 200 );
		textFont(fontTree);
		textAlign(CENTER, CENTER);
			
		fill(0);
		noFill();
			
		stroke(0);
		strokeWeight(1.5);
			
		pushMatrix();
		{			
			
			switch (drawMode)
			{
			case DRAW_MODE_ICICLE:

				translate( 70.0, (float) height / 2.0 - (float) NODE_HEIGHT * 7.0 / 2.0);
				renderIcicle( theTree, hilight_map );
				break;
				
			case DRAW_MODE_GRAPH:
				//translate( 70.0, (float) height / 2.0 - (float) GRAPH_NODE_HEIGHT * 7.0 / 2.0);
				
				translate( -( theTree.global_maxChild ) / 2, - (GRAPH_NODE_HEIGHT * theTree.treeDepth) / 2);
				translate( width / 2 - 30 , height / 2 );
				
				renderGraph( theTree, hilight_map );
				break;
			
			case DRAW_MODE_TREEMAP:	
				translate( - TREEMAP_WIDTH / 2, - TREEMAP_HEIGHT / 2 );
				translate( width / 2, height / 2 );
				
				treeMap.draw();
				break;
			}
		}
		popMatrix();
		
		// if there flag response error then render red overlay
		if (flagBadResponse)
		{
			fill(#FF5D5D, 128);
			rect(0, 0, width, height);
			
			if (millis() - badTime > 700) {
				// clear bad response flag
				flagBadResponse = false;
			}
		}
		
		
	}
	
	void renderResponse()
	{
		background( QUESTION_BACKGROUND );
		textFont(fontQuestion);
		textAlign(CENTER, CENTER);
		
		fill( QUESTION_FOREGROUND );
		stroke( QUESTION_FOREGROUND );
		
		text("Type answer", width/2, height/3);
		
		if (strResponse.length() > 0)
		{
			text(strResponse, width / 2, 2*height/3);
		}
	}
	
	void renderTransition()
	{
		background( QUESTION_BACKGROUND );
		
		fill(255); noStroke();
		
		// an illipse in the center
		ellipse( width/2, height/2, FOCUS_ELLIPSE, FOCUS_ELLIPSE );
		
		if (getTime() - startTime > TRANSITION_TIME)
		{
			state = PHASE_FINISHED;
		}
	}
	
	void keyPressed()
	{
		switch (state)
		{
		case PHASE_QUESTION:
			if (key == ' ' || key == ENTER && (getTime() - startTime > 1.0)) 
			{
				state = PHASE_VIS;
				readTime = getTime() - startTime;
				startTime = getTime();
				visTimestamp = millis();
			}
			break;
		
		case PHASE_VIS:
		
			if (flagBadResponse) {
				// ignore input in flag bad resposne is on
				break;
			}
			
			if (taskType == TASK_BINARY_ANSWER)
			{
				boolean registered = false;
				if (key == 'q' || key == 'Q' || key == 'y' || key == 'Y') {
					strResponse = "yes";
					registered = true;
				}
				else if (key == 'p' || key == 'P' || key == 'n' || key == 'N') {
					strResponse = "no";
					registered = true;
				}
				
				if (training)
				{
					// make sure answer is correct
					if (strResponse.compareToIgnoreCase(this.answer) != 0)
					{
						flagBadResponse = true;
						badTime = millis();
						break;
					}
				}
				
				if (registered)
				{
					reasonTime = getTime() - startTime;
					responseTime = 0;
					startTime = getTime();
					
					state = PHASE_TRANSITION;
					transitionTimestamp = millis();
					responseTimestamp = millis();
				}
			}
			else
			{
				if ((key == ' ' || key == ENTER) && (getTime() - startTime > 1.0))
				{
					state = PHASE_RESPONSE;
					reasonTime = getTime() - startTime;
					startTime = getTime();
					responseTimestamp = millis();
				}
			}
			
			break;
			
		case PHASE_RESPONSE:
		
			if (key == ENTER && strResponse.length() > 0) 
			{
				state = PHASE_TRANSITION;
				responseTime = getTime() - startTime;
				startTime = getTime();
				transitionTimestamp = millis();
			
			}
			else if (key == BACKSPACE) 
			{
				if (strResponse.length() > 0) 
				{
					strResponse = strResponse.substring(0, strResponse.length() - 1);
				}
			}
			else if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z') || (key >= '0' && key <= '9')) 
			{
				strResponse += key;
				strResponse = strResponse.toUpperCase();
			}
			break;
			
		}	
	}
}

/* ------------------------------------------------------------------- */
static final int PARSE_STIMULUS = 1;

class StimuliList
{
	// list of stimuli
	ArrayList<Stimulus> stimuli;
	
	StimuliList(String filename)
	{
		stimuli = new ArrayList<Stimulus>();
		
		String[] data = loadStrings(filename);
		parseData( data );
		
		// shuffle stimuli
		if (randomize_stimuli)
		{
			for (int i = 0; i < 10000; i++)
			{
				int i1 = int(random(stimuli.size()));
				int i2 = int(random(stimuli.size()));
				
				Stimulus temp = stimuli.get( i1 );
				stimuli.set( i1, stimuli.get( i2 ));
				stimuli.set( i2, temp );
			}
		}
	}
	
	void parseData(String[] data)
	{
		
		boolean haveData = false;
		
		String name = null;
		String tree = null;
		String question = null;
		String answer = null;
		boolean compatible = false;
		
		int drawMode = 0;
		int taskType = 0;
		
		// sequential stimulus number
		int stimulusNumber = 0;
		
		ArrayList<String> hilights = new ArrayList<String>();
		
		for (int i = 0; i < data.length; i++)
		{
			if (data[i] == null) {
				continue;
			}
			
			String l = trim(data[i]);
			if (l.length() == 0) {
				// ignore empty lines
				continue;
			}
			else if (l.charAt(0) == '#') {
				// ignore comments
				continue;
			}
			
			String[] tokens = split(l, "=");
			String strKey = trim(tokens[0]);
			String strValue = trim(tokens[1]);
			
			if (strKey.compareToIgnoreCase("name") == 0)
			{
				if (haveData)
				{
					// add the last stimulus
					Stimulus s = new Stimulus( stimulusNumber++, name, tree, question, hilights, drawMode, answer, taskType );
					s.compatible = compatible;
					stimuli.add( s );
					haveData = false;
				}
				name = strValue;
				hilights = new ArrayList<String>();

			}
			else if (strKey.compareToIgnoreCase("draw") == 0)
			{
				if (strValue.compareToIgnoreCase("graph") == 0)
				{
					drawMode = DRAW_MODE_GRAPH;
				}
				else if (strValue.compareToIgnoreCase("icicle") == 0)
				{
					drawMode = DRAW_MODE_ICICLE;
				}
				else if (strValue.compareToIgnoreCase("treemap") == 0)
				{
					drawMode = DRAW_MODE_TREEMAP;
				}
			}
			else if (strKey.compareToIgnoreCase("tree") == 0)
			{
				tree = strValue;
			}
			else if (strKey.compareToIgnoreCase("hilight") == 0)
			{
				hilights.add( strValue.toUpperCase() );
			}
			else if (strKey.compareToIgnoreCase("question") == 0)
			{
				question = strValue;
			}
			else if (strKey.compareToIgnoreCase("answer") == 0)
			{
				answer = strValue.toUpperCase();
				haveData = true;
			}
			else if (strKey.compareToIgnoreCase("compatible") == 0)
			{
				compatible = strValue.compareToIgnoreCase("yes") == 0;
			}
			
			else if (strKey.compareToIgnoreCase("task") == 0)
			{
				if (strValue.compareToIgnoreCase("count-children") == 0)
				{
					taskType = TASK_COUNT_CHILDREN;
				}
				else if (strValue.compareToIgnoreCase("shared-parent") == 0)
				{
					taskType = TASK_SHARED_PARENT;
				}
				else if (strValue.compareToIgnoreCase("largets-children") == 0)
				{
					taskType = TASK_LARGEST_CHILDREN;
				}
				else
				{
					taskType = TASK_BINARY_ANSWER;
				}
				
			}
			else
			{
				println("WARNING: invalid line in stimuli file: " + l);
			}
		}
		
		if (haveData) {
			// add last stimulus
			Stimulus s = new Stimulus( stimulusNumber++, name, tree, question, hilights, drawMode, answer, taskType );
			s.compatible = compatible;
			stimuli.add( s );
		}
	}
	
	void printStimuli()
	{
		for (int i = 0; i < stimuli.size(); i++)
		{
			Stimulus s = stimuli.get(i);
			println("Stimulus: " + s.name);
			println("\t tree: " + s.treeFile);
			println("\t draw mode: " + (s.drawMode == DRAW_MODE_GRAPH ? "graph" : (s.drawMode == DRAW_MODE_TREEMAP ? "treemap" : "icicle")));
			println("\t question: " + s.question);
			
			// print hilights
			for (int j = 0; j < s.hilights.size(); j++)
			{
				println("\t\thilight: \t" + s.hilights.get(j));
			}
			println("==============\n");
		}	
	}
	
	int size()
	{
		return stimuli.size();
	}
	
	Stimulus get( int index )
	{
		return stimuli.get( index );
	}
	
	void sort()
	{
		Collections.sort( stimuli, new TaskComparator() );
	}
}

class TaskComparator implements Comparator
{
	public int compare(Object o1, Object o2)
	{
		Stimulus t1 = (Stimulus) o1;
		Stimulus t2 = (Stimulus) o2;
    
		if (t1.number > t2.number)
		{
			return 1;
		}
		else if (t1.number < t2.number)
		{
			return -1;
		}
		else
		{
			return 0;
		}
	}
}

